﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

using System.IO;
using FerrScript.Builder;

namespace FerrLispBuilderControl
{
    public partial class FerrScriptBuilderControl : UserControl
    {
        #region Fields
        int          addIndex;
        ListViewItem addItem;
        #endregion

        #region Constructor
        public FerrScriptBuilderControl()
        {
            InitializeComponent();
            Disable();
        }
        #endregion

        #region Events
        private void FerrLispBuilderControl_Load(object sender, EventArgs e)
        {
            if (!this.DesignMode)
            {
                List<BuilderBlock> blocks = GetBlockTypes();

                RebuildTabs(blocks);
            }
        }

        private void view_DragEnter    (object sender, DragEventArgs     e)
        {
            if (e.Data.GetDataPresent(typeof(ListViewItem)))
                e.Effect = DragDropEffects.Copy;
            else
                e.Effect = DragDropEffects.None;
        }
        private void view_ItemDrag     (object sender, ItemDragEventArgs e)
        {
            ((ListView)sender).DoDragDrop(e.Item, DragDropEffects.Copy);
        }
        private void cancelButton_Click(object sender, EventArgs         e)
        {
            ((Form)((Button)sender).Parent).Close();
        }
        private void okButton_Click    (object sender, EventArgs         e)
        {
            Form form = (Form)((Button)sender).Parent;
            Type type = (Type)form.Tag.GetType();

            object block = form.Tag;
            string propertyName = "";

            for (int i = 0; i < form.Controls.Count; i++)
            {
                if (form.Controls[i] is TextBox)
                {
                    propertyName = (string)form.Controls[i].Tag;
                    PropertyInfo property = type.GetProperty(propertyName);

                    if (property.PropertyType == typeof(string))
                        property.SetValue(block, form.Controls[i].Text, null);
                    else if (property.PropertyType == typeof(int))
                        property.SetValue(block, int.Parse(form.Controls[i].Text), null);
                    else if (property.PropertyType == typeof(long))
                        property.SetValue(block, long.Parse(form.Controls[i].Text), null);
                    else if (property.PropertyType == typeof(float))
                        property.SetValue(block, float.Parse(form.Controls[i].Text), null);
                    else if (property.PropertyType == typeof(double))
                        property.SetValue(block, double.Parse(form.Controls[i].Text), null);
                    else
                        throw new NotImplementedException("FerrLisp Builder does not support the type for " + type.Name + "." + propertyName + ", try string, int, long, float or double.");
                }
            }

            if (addItem == null)
            {
                ListViewItem item = new ListViewItem(((BuilderBlock)block).Description, ((BuilderBlock)block).IconName);
                item.Tag = block;
                commandListView.Items.Insert(addIndex, item);
            }
            else
            {
                addItem.Text = ((BuilderBlock)block).Description;
            }

            form.Close();
        }

        private void commandListView_ItemDrag        (object sender, ItemDragEventArgs e)
        {
            //commandListView.Items.Remove((ListViewItem)e.Item);
            commandListView.DoDragDrop(e.Item, DragDropEffects.Move);
        }
        private void commandListView_DragDrop        (object sender, DragEventArgs     e)
        {   
            Point        local    = commandListView.PointToClient(new Point(e.X, e.Y));
            ListViewItem location = commandListView.GetItemAt(local.X, local.Y);

            int index = commandListView.Items.Count;
            if (location != null)
                index = commandListView.Items.IndexOf(location);

            ListViewItem item = (ListViewItem)e.Data.GetData(typeof(ListViewItem));
            if (e.Effect == DragDropEffects.Copy)
            {
                addItem = null;

                // go ahead and add an from the block
                ShowNewBlockWindow(index, (BuilderBlock)Activator.CreateInstance(item.Tag.GetType()));
            }
            else if (e.Effect == DragDropEffects.Move)
            {
                commandListView.Items.Remove(item);
                if (index > commandListView.Items.Count)
                    index = commandListView.Items.Count;
                commandListView.Items.Insert(index, item);
            }
        }
        private void commandListView_DragEnter       (object sender, DragEventArgs     e)
        {
            if (e.Data.GetDataPresent(typeof(ListViewItem)))
            {
                if (e.AllowedEffect == DragDropEffects.Copy)
                    e.Effect = DragDropEffects.Copy;
                else
                    e.Effect = DragDropEffects.Move;
            }
            else
                e.Effect = DragDropEffects.None;
        }
        private void commandListView_MouseDoubleClick(object sender, MouseEventArgs    e)
        {
            if (commandListView.SelectedItems.Count > 0)
            {
                addIndex = commandListView.Items.IndexOf(commandListView.SelectedItems[0]);
                addItem = commandListView.SelectedItems[0];

                ShowNewBlockWindow(addIndex, (BuilderBlock)addItem.Tag);
            }
        }

        private void editToolStripMenuItem_Click    (object sender, EventArgs e)
        {
            if (commandListView.SelectedItems.Count >= 1)
            {
                ListViewItem location = commandListView.SelectedItems[0];

                int index = commandListView.Items.Count;
                if (location != null)
                    index = commandListView.Items.IndexOf(location);

                addItem = location;

                ShowNewBlockWindow(index, (BuilderBlock)location.Tag);
            }
        }
        private void deleteToolStripMenuItem_Click  (object sender, EventArgs e)
        {
            if (commandListView.SelectedItems.Count >= 1)
            {
                commandListView.Items.Remove(commandListView.SelectedItems[0]);
            }
        }
        private void moveUpToolStripMenuItem_Click  (object sender, EventArgs e)
        {
            if (commandListView.SelectedItems.Count >= 1)
            {
                ListViewItem item = commandListView.SelectedItems[0];
                int index = commandListView.Items.IndexOf(item);
                index -= 1;
                if (index < 0)
                    index = 0;
                commandListView.Items.Remove(item);
                commandListView.Items.Insert(index, item);
            }
        }
        private void moveDownToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (commandListView.SelectedItems.Count >= 1)
            {
                ListViewItem item = commandListView.SelectedItems[0];
                int index = commandListView.Items.IndexOf(item);
                index += 1;
                if (index > commandListView.Items.Count - 1)
                    index = commandListView.Items.Count - 1;
                commandListView.Items.Remove(item);
                commandListView.Items.Insert(index, item);
            }
        }
        #endregion

        #region Helping methods
        private void ShowNewBlockWindow(int aIndex, BuilderBlock aBlockToEdit)
        {
            // variables for easy sizing
            int startY       = 10;
            int x            = 10;
            int y            = startY;
            int height       = 20;
            int heightGap    = 5;
            int labelWidth   = 100;
            int boxWidth     = 200;
            int buttonHeight = 25;
            int buttonWidth  = 80;

            Form addBlockForm = new Form();
            addBlockForm.Text = "New " + aBlockToEdit.BlockName;
            addBlockForm.FormBorderStyle = FormBorderStyle.FixedToolWindow;
            addBlockForm.Tag = aBlockToEdit;
            
            Type type = aBlockToEdit.GetType();
            int count = 0;
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.CanWrite)
                {
                    if (false) { }
                    else
                    {
                        Label label = new Label();
                        label.Text = property.Name;
                        label.Location = new Point(x, y);
                        label.Size = new Size(labelWidth, height);

                        TextBox box = new TextBox();
                        box.Location = new Point(x + labelWidth, y);
                        box.Size = new Size(boxWidth, height);
                        box.Tag = "" + property.Name;
                        box.Text = property.GetValue(aBlockToEdit, null).ToString();

                        addBlockForm.Controls.Add(label);
                        addBlockForm.Controls.Add(box);
                    }

                    y += height + heightGap;
                    count++;
                }
            }
            int finalWidth  = boxWidth + labelWidth + x * 2;
            int finalHeight = (heightGap + height) * count + 2 * startY;

            Button okButton     = new Button();
            Button cancelButton = new Button();
            okButton.Text     = "OK";
            cancelButton.Text = "Cancel";
            okButton.Location     = new Point(finalWidth - (buttonWidth * 2 + heightGap * 3), y);
            cancelButton.Location = new Point(finalWidth - (buttonWidth + heightGap * 2), y);
            okButton.Size = cancelButton.Size = new Size(buttonWidth, buttonHeight);
            okButton.Click     += new EventHandler(okButton_Click);
            cancelButton.Click += new EventHandler(cancelButton_Click);

            addBlockForm.Controls.Add(okButton);
            addBlockForm.Controls.Add(cancelButton);
            addBlockForm.AcceptButton = okButton;
            addBlockForm.CancelButton = cancelButton;

            finalHeight += buttonHeight + heightGap;

            addBlockForm.Size = new Size(finalWidth, finalHeight + 12);

            addIndex = aIndex;

            if (count <= 0)
                okButton_Click(okButton, null);
            else
                addBlockForm.ShowDialog();
        }

        private List<BuilderBlock> GetBlockTypes()
        {
            List<BuilderBlock> classTypes       = new List<BuilderBlock>();
            Type               builderBlockType = typeof(BuilderBlock);

            AppDomain  MyDomain         = AppDomain.CurrentDomain;
            Assembly[] AssembliesLoaded = MyDomain.GetAssemblies();
            foreach (Assembly MyAssembly in AssembliesLoaded)
            {
                if (MyAssembly.GetName().Name == "FerrScript") 
                {
                }
                foreach (Type type in MyAssembly.GetTypes())
                {
                    if (type.IsSubclassOf(builderBlockType))
                        classTypes.Add((BuilderBlock)Activator.CreateInstance( type ));
                }
            }

            return classTypes;
        }
        private void               RebuildTabs(List<BuilderBlock> aBlockTypes)
        {
            commandTabs.TabPages.Clear();

            List<string> groupNames = new List<string>();
            // go ahead and figure out unique groups
            for (int i = 0; i < aBlockTypes.Count; i++)
            {
                if (!groupNames.Contains(aBlockTypes[i].BlockType.ToString()))
                    groupNames.Add(aBlockTypes[i].BlockType.ToString());
            }

            // make sure all images are loaded
            for (int i = 0; i < aBlockTypes.Count; i++)
            {
                if (!iconImageList.Images.ContainsKey(aBlockTypes[i].IconName))
                {
                    Image img = LoadImage(aBlockTypes[i].IconName);

                    iconImageList.Images.Add(aBlockTypes[i].IconName, img);
                }
            }

            // create a new list of tabs
            for (int i = 0; i < groupNames.Count; i++)
            {
                TabPage page = new TabPage(groupNames[i]);

                // create the list view for this group of commands
                ListView view = new ListView();
                view.LargeImageList = iconImageList;
                view.SmallImageList = iconImageList;
                view.View = View.LargeIcon;
                view.Dock = DockStyle.Fill;
                view.Location = new Point(3, 3);
                view.Size = new Size(20, 20);
                view.AllowDrop = true;
                view.ItemDrag += new ItemDragEventHandler(view_ItemDrag);
                view.DragEnter += new DragEventHandler(view_DragEnter);

                // add blocks to the view
                for (int b = 0; b < aBlockTypes.Count; b++)
                {
                    if (aBlockTypes[b].BlockType.ToString() == groupNames[i])
                    {
                        ListViewItem item = new ListViewItem(aBlockTypes[b].BlockName, aBlockTypes[b].IconName);
                        item.Tag = aBlockTypes[b];
                        view.Items.Add(item);
                    }
                }

                // add it as a control to the panel
                page.Controls.Add(view);

                // add the panel to the list of tabs
                commandTabs.TabPages.Add(page);
            }
        }
        private Image              LoadImage(string aIconName)
        {
            AppDomain  MyDomain         = AppDomain.CurrentDomain;
            Assembly[] AssembliesLoaded = MyDomain.GetAssemblies();
            Stream  s = null;
            foreach (Assembly MyAssembly in AssembliesLoaded)
            {
                string resourceName = "";
                string[] resources = MyAssembly.GetManifestResourceNames();
                for (int i = 0; i < resources.Length; i++)
                {
                    if (resources[i].EndsWith(aIconName))
                        resourceName = resources[i];
                }
                if (resourceName != "")
                    s = MyAssembly.GetManifestResourceStream(resourceName);
                if (s != null)
                    break;
            }
            if (s == null)
            {
                if (File.Exists("Icons\\"+aIconName))
                    s = File.Open("Icons\\"+aIconName, FileMode.Open);
            }

            if (s == null)
                throw new Exception("Unable to find icon as an embedded resource or in the Icons folder! " + aIconName);

            return Image.FromStream(s);
        }
        #endregion

        #region Enabling
        private void Enable ()
        {
            Enabled = true;
            commandListView.Enabled = true;
            commandTabs.Enabled = true;
        }
        private void Disable()
        {
            Enabled = false;
            commandListView.Enabled = false;
            commandTabs.Enabled = false;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Gets a list of the BuilderBlocks that are represented in the control
        /// </summary>
        /// <returns>List containing references to the blocks in the control</returns>
        public List<BuilderBlock> GetBlockList()
        {
            List<BuilderBlock> blocks = new List<BuilderBlock>();
            foreach (ListViewItem item in commandListView.Items)
            {
                blocks.Add((BuilderBlock)item.Tag);
            }
            return blocks;
        }
        /// <summary>
        /// Clears the existing blocks from the control, and adds in the blocks
        /// in the given list.
        /// </summary>
        /// <param name="aValue">A list of blocks to replace the existing blocks</param>
        public void               SetBlockList(List<BuilderBlock> aNewBlocks)
        {
            // check for enabling of the form
            if (aNewBlocks != null)
                Enable();
            else
                Disable();

            // update the command list
            commandListView.Items.Clear();
            foreach (BuilderBlock block in aNewBlocks)
            {
                ListViewItem item = new ListViewItem(block.Description, block.IconName);
                item.Tag = block;
                commandListView.Items.Add(item);
            }
        }
        #endregion
    }
}
